package com.spider.moudle.factory;

import com.google.common.collect.Lists;
import com.spider.moudle.api.Downloader;
import com.spider.moudle.api.LoginDownloader;
import com.spider.moudle.api.realize.StandardDownloader;
import com.spider.moudle.api.realize.StandardLoginDownloader;
import com.spider.moudle.constant.Constant;
import com.spider.moudle.entity.Cookie;
import com.spider.moudle.entity.ResponseEntity;

import com.spider.moudle.util.ErrorLogUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpVersion;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.*;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 夏茂轩
 * @description 下载器工厂
 * @date 2017/11/10 10:36
 */
public class DownloaderFactory {
    private static Log logger1 = LogFactory.getLog("access");
    private static Collection<Header> defaultHeaders = Lists.newArrayList(new BasicHeader("User-Agent", Constant.HTTP_USER_AGENT)
            , new BasicHeader("Accept-Encoding", Constant.ACCEPT_ENCODING));
    public static Downloader buildDownloader() {
        StandardDownloader standardDownloader = buildDownloader(new Object[0]);
        InvocationHandler invocationHandler = new DownloaderInvocationHandler(standardDownloader);
        Downloader downloader = (Downloader) Proxy.newProxyInstance(standardDownloader.getClass().getClassLoader(),
                standardDownloader.getClass().getInterfaces(), invocationHandler);
        return downloader;
    }

    public static Downloader buildDownloader(String encoding) {
        StandardDownloader standardDownloader = buildDownloader(new Object[]{encoding});
        InvocationHandler invocationHandler = new DownloaderInvocationHandler(standardDownloader);
        Downloader downloader = (Downloader) Proxy.newProxyInstance(standardDownloader.getClass().getClassLoader(),
                standardDownloader.getClass().getInterfaces(), invocationHandler);
        return downloader;
    }

    public static Downloader buildDownloader(Map<String, String> headers) {
        StandardDownloader standardDownloader = buildDownloader(new Object[]{headers});
        InvocationHandler invocationHandler = new DownloaderInvocationHandler(standardDownloader);
        Downloader downloader = (Downloader) Proxy.newProxyInstance(standardDownloader.getClass().getClassLoader(),
                standardDownloader.getClass().getInterfaces(), invocationHandler);
        return downloader;
    }

    public static Downloader buildDownloader(Map<String, String> headers, String encoding) {
        StandardDownloader standardDownloader = buildDownloader(new Object[]{headers, encoding});
        InvocationHandler invocationHandler = new DownloaderInvocationHandler(standardDownloader);
        Downloader downloader = (Downloader) Proxy.newProxyInstance(standardDownloader.getClass().getClassLoader(),
                standardDownloader.getClass().getInterfaces(), invocationHandler);
        return downloader;
    }

    private static StandardDownloader buildDownloader(Object[] objects) {
        Class clzz = StandardDownloader.class;
        Class[] classes = null;
        classes = new Class[objects.length];
        for (int i = 0; i < classes.length; i++) {
            classes[i] = objects[i].getClass();
        }
        Constructor constructor = null;
        try {
            constructor = clzz.getDeclaredConstructor(classes);
            constructor.setAccessible(true);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        StandardDownloader downloader = null;
        if (constructor != null) {
            try {
                downloader = (StandardDownloader) constructor.newInstance(objects);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        return downloader;
    }

    public static LoginDownloader buildLoginDownloader(ArrayList<Cookie> cookies) {
        StandardLoginDownloader loginDownloader = buildLoginDownloader(new Object[]{cookies});
        InvocationHandler invocationHandler = new DownloaderInvocationHandler(loginDownloader);
        LoginDownloader downloader = (LoginDownloader) Proxy.newProxyInstance(loginDownloader.getClass().getClassLoader(),
                loginDownloader.getClass().getInterfaces(), invocationHandler);
        return downloader;
    }

    public static LoginDownloader buildLoginDownloader(ArrayList<Cookie> cookies, String encoding) {
        StandardLoginDownloader loginDownloader = buildLoginDownloader(new Object[]{cookies, encoding});
        InvocationHandler invocationHandler = new DownloaderInvocationHandler(loginDownloader);
        LoginDownloader downloader = (LoginDownloader) Proxy.newProxyInstance(loginDownloader.getClass().getClassLoader(),
                loginDownloader.getClass().getInterfaces(), invocationHandler);
        return downloader;
    }

    public static LoginDownloader buildLoginDownloader(ArrayList<Cookie> cookies, ArrayList<Header> headers) {
        StandardLoginDownloader loginDownloader = buildLoginDownloader(new Object[]{cookies, headers});
        InvocationHandler invocationHandler = new DownloaderInvocationHandler(loginDownloader);
        LoginDownloader downloader = (LoginDownloader) Proxy.newProxyInstance(loginDownloader.getClass().getClassLoader(),
                loginDownloader.getClass().getInterfaces(), invocationHandler);
        return downloader;
    }

    public static LoginDownloader buildLoginDownloader(ArrayList<Cookie> cookies, ArrayList<Header> headers, String encoding) {
        StandardLoginDownloader loginDownloader = buildLoginDownloader(new Object[]{cookies, headers, encoding});
        InvocationHandler invocationHandler = new DownloaderInvocationHandler(loginDownloader);
        LoginDownloader downloader = (LoginDownloader) Proxy.newProxyInstance(loginDownloader.getClass().getClassLoader(),
                loginDownloader.getClass().getInterfaces(), invocationHandler);
        return downloader;
    }

    /**
     * @param objects LoginDownloader的构造函数参数
     * @return com.spider.moudle.api.realize.StandardLoginDownloader
     * @author 夏茂轩
     * @description 使用动态代理，生成代理类，
     * @date 2017/11/14 10:30
     */
    private static StandardLoginDownloader buildLoginDownloader(Object[] objects) {
        Class clzz = StandardLoginDownloader.class;
        Class[] classes = null;
        classes = new Class[objects.length];
        for (int i = 0; i < classes.length; i++) {
            classes[i] = objects[i].getClass();
        }
        Constructor constructor = null;
        try {
            constructor = clzz.getDeclaredConstructor(classes);
            constructor.setAccessible(true);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        StandardLoginDownloader downloader = null;
        if (constructor != null) {
            try {
                downloader = (StandardLoginDownloader) constructor.newInstance(objects);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        return downloader;
    }


    public static HttpUriRequest buildRequest(String url, String method, Map<String, String> params, RequestConfig config, List<Header> headers, String encoding) throws Exception {
        RequestBuilder builder = RequestBuilder.create(method.toUpperCase()).setUri(url);
        List<Header> Hlist=Lists.newArrayList();
        Hlist.addAll(headers);
        Hlist.addAll(defaultHeaders);
        for (Header header : Hlist) {
            builder.addHeader(header);
        }
        List<BasicNameValuePair> list = Lists.newArrayList();
        UrlEncodedFormEntity encodedFormEntity = null;
        if (params != null && params.size() > 0) {
            params.entrySet().forEach(stringStringEntry -> {
                list.add(new BasicNameValuePair(stringStringEntry.getKey(), stringStringEntry.getValue()));
            });
            encodedFormEntity = new UrlEncodedFormEntity(list, "UTF-8");
        }
        if (encodedFormEntity != null) {
            builder.setEntity(encodedFormEntity);
        }
        builder.setVersion(HttpVersion.HTTP_1_0);
        if(encoding!=null){
            builder.setCharset(Charset.forName(encoding));
        }
        builder.setConfig(config);
        return builder.build();
    }

    public static ResponseEntity execute(HttpUriRequest request, CloseableHttpClient client, com.spider.moudle.entity.Proxy proxy, String encoding) throws Exception {
        CloseableHttpResponse response = null;
        InputStream stream = null;
        ResponseEntity responseEntity = null;
        HttpEntity entity = null;
        try {
            long start = System.currentTimeMillis();
            response = client.execute(request);
            entity = response.getEntity();
            String content = EntityUtils.toString(entity, Charset.forName(encoding));
            int status_code = response.getStatusLine().getStatusCode();
            long end = System.currentTimeMillis();
            if (proxy != null) {
                responseEntity = new ResponseEntity(request.getURI().toString(), content, status_code, headerTranslate(response.getAllHeaders()), encoding, end - start, proxy.getIp() + ":" + proxy.getPort());
            } else {
                responseEntity = new ResponseEntity(request.getURI().toString(), content, status_code, headerTranslate(response.getAllHeaders()), encoding, end - start);
            }
            return responseEntity;
        } finally {
            handleAfter(response, stream, request,client);
            EntityUtils.consumeQuietly(entity);
        }
    }

    public static ResponseEntity executeSource(HttpUriRequest request, CloseableHttpClient client, com.spider.moudle.entity.Proxy proxy, String encoding) throws Exception {
        CloseableHttpResponse response = null;
        InputStream stream = null;
        ResponseEntity responseEntity = null;
        HttpEntity entity = null;
        try {
            long start = System.currentTimeMillis();
            response = client.execute(request);
            entity = response.getEntity();
            int status_code = response.getStatusLine().getStatusCode();
            long end = System.currentTimeMillis();
            stream=entity.getContent();
            if (proxy != null) {
                responseEntity = new ResponseEntity(request.getURI().toString(), status_code, headerTranslate(response.getAllHeaders()), end - start, proxy.getIp() + ":" + proxy.getPort(),IOUtils.toByteArray(stream));
            } else {
                responseEntity = new ResponseEntity(request.getURI().toString(), status_code, headerTranslate(response.getAllHeaders()), end - start,null,IOUtils.toByteArray(stream));
            }
            return responseEntity;
        } finally {
            handleAfter(response, stream, request,client);
            EntityUtils.consumeQuietly(entity);
        }
    }

    public static ArrayList<Header> headerTranslate(Header[] headers) {
        ArrayList<Header> x = Arrays.asList(headers).stream().collect(Collectors.toCollection(ArrayList::new));
        return x;
    }

    public static boolean isHttps(String url) {
        if (url.toLowerCase().indexOf("https") != -1) {
            return true;
        } else {
            return false;
        }
    }

    public static class DownloaderInvocationHandler implements InvocationHandler {
        public Object getS() {
            return s;
        }

        public void setS(Object s) {
            this.s = s;
        }

        private Object s;

        public DownloaderInvocationHandler(Object s) {
            this.s = s;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            try {
                Object result = method.invoke(s, args);
                if (result instanceof ResponseEntity) {
                    logger1.info(result);
                }
                return result;
            } catch (Exception e) {
                if (e.getCause() instanceof SocketTimeoutException || e.getCause() instanceof ConnectTimeoutException) {
                } else {
                }
                throw e.getCause();
            }
        }
    }

    private static void handleAfter(CloseableHttpResponse response, InputStream inputStream, HttpUriRequest request,CloseableHttpClient client) throws Exception {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (Exception e) {
                throw e;
            }
        }
        if (response != null) {
            try {
                response.close();
            } catch (Exception e) {
                throw e;
            }
        }
        if (request != null) {
            try {
                request.abort();
            } catch (Exception e) {
                throw e;
            }
        }
        if(client!=null){
            /*try {
                client.close();
            } catch (IOException e) {
                throw e;
            }*/
        }
    }

    public static void handClient(CloseableHttpClient httpClient){
        if(httpClient!=null){
            try {
                httpClient.close();
            } catch (Exception e) {
                ErrorLogUtil.error(e.getMessage(),e);
            }
        }
    }
}
